//
// Copyright 2017 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

#import "TSThread.h"
#import "OWSDisappearingMessagesConfiguration.h"
#import "OWSReadTracking.h"
#import "TSIncomingMessage.h"
#import "TSInfoMessage.h"
#import "TSInteraction.h"
#import "TSInvalidIdentityKeyReceivingErrorMessage.h"
#import "TSOutgoingMessage.h"
#import <SignalServiceKit/SignalServiceKit-Swift.h>

@import Intents;

NS_ASSUME_NONNULL_BEGIN

@interface TSThread ()

@property (nonatomic, nullable) NSDate *creationDate;
@property (nonatomic) BOOL isArchivedObsolete;
@property (nonatomic) BOOL isMarkedUnreadObsolete;

@property (atomic) uint64_t mutedUntilTimestampObsolete;

@property (nonatomic, nullable) NSDate *mutedUntilDateObsolete;
@property (nonatomic) uint64_t lastVisibleSortIdObsolete;
@property (nonatomic) double lastVisibleSortIdOnScreenPercentageObsolete;

@end

#pragma mark -

@implementation TSThread

- (instancetype)initWithUniqueId:(NSString *)uniqueId
{
    self = [super initWithUniqueId:uniqueId];

    if (self) {
        _creationDate = [NSDate date];
        _messageDraft = nil;
        _conversationColorNameObsolete = @"Obsolete";
    }

    return self;
}

// --- CODE GENERATION MARKER

// This snippet is generated by /Scripts/sds_codegen/sds_generate.py. Do not manually edit it, instead run
// `sds_codegen.sh`.

// clang-format off

- (instancetype)initWithGrdbId:(int64_t)grdbId
                      uniqueId:(NSString *)uniqueId
   conversationColorNameObsolete:(NSString *)conversationColorNameObsolete
                    creationDate:(nullable NSDate *)creationDate
             editTargetTimestamp:(nullable NSNumber *)editTargetTimestamp
              isArchivedObsolete:(BOOL)isArchivedObsolete
          isMarkedUnreadObsolete:(BOOL)isMarkedUnreadObsolete
       lastDraftInteractionRowId:(uint64_t)lastDraftInteractionRowId
        lastDraftUpdateTimestamp:(uint64_t)lastDraftUpdateTimestamp
            lastInteractionRowId:(uint64_t)lastInteractionRowId
          lastSentStoryTimestamp:(nullable NSNumber *)lastSentStoryTimestamp
       lastVisibleSortIdObsolete:(uint64_t)lastVisibleSortIdObsolete
lastVisibleSortIdOnScreenPercentageObsolete:(double)lastVisibleSortIdOnScreenPercentageObsolete
         mentionNotificationMode:(TSThreadMentionNotificationMode)mentionNotificationMode
                    messageDraft:(nullable NSString *)messageDraft
          messageDraftBodyRanges:(nullable MessageBodyRanges *)messageDraftBodyRanges
          mutedUntilDateObsolete:(nullable NSDate *)mutedUntilDateObsolete
     mutedUntilTimestampObsolete:(uint64_t)mutedUntilTimestampObsolete
           shouldThreadBeVisible:(BOOL)shouldThreadBeVisible
                   storyViewMode:(TSThreadStoryViewMode)storyViewMode
{
    self = [super initWithGrdbId:grdbId
                        uniqueId:uniqueId];

    if (!self) {
        return self;
    }

    _conversationColorNameObsolete = conversationColorNameObsolete;
    _creationDate = creationDate;
    _editTargetTimestamp = editTargetTimestamp;
    _isArchivedObsolete = isArchivedObsolete;
    _isMarkedUnreadObsolete = isMarkedUnreadObsolete;
    _lastDraftInteractionRowId = lastDraftInteractionRowId;
    _lastDraftUpdateTimestamp = lastDraftUpdateTimestamp;
    _lastInteractionRowId = lastInteractionRowId;
    _lastSentStoryTimestamp = lastSentStoryTimestamp;
    _lastVisibleSortIdObsolete = lastVisibleSortIdObsolete;
    _lastVisibleSortIdOnScreenPercentageObsolete = lastVisibleSortIdOnScreenPercentageObsolete;
    _mentionNotificationMode = mentionNotificationMode;
    _messageDraft = messageDraft;
    _messageDraftBodyRanges = messageDraftBodyRanges;
    _mutedUntilDateObsolete = mutedUntilDateObsolete;
    _mutedUntilTimestampObsolete = mutedUntilTimestampObsolete;
    _shouldThreadBeVisible = shouldThreadBeVisible;
    _storyViewMode = storyViewMode;

    return self;
}

// clang-format on

// --- CODE GENERATION MARKER

- (nullable instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (!self) {
        return self;
    }

    // renamed `hasEverHadMessage` -> `shouldThreadBeVisible`
    if (!_shouldThreadBeVisible) {
        NSNumber *_Nullable legacy_hasEverHadMessage = [coder decodeObjectForKey:@"hasEverHadMessage"];

        if (legacy_hasEverHadMessage != nil) {
            _shouldThreadBeVisible = legacy_hasEverHadMessage.boolValue;
        }
    }

    if (_conversationColorNameObsolete.length == 0) {
        _conversationColorNameObsolete = @"Obsolete";
    }

    NSDate *_Nullable lastMessageDate = [coder decodeObjectOfClass:NSDate.class forKey:@"lastMessageDate"];
    NSDate *_Nullable archivalDate = [coder decodeObjectOfClass:NSDate.class forKey:@"archivalDate"];
    _isArchivedByLegacyTimestampForSorting = [self.class legacyIsArchivedWithLastMessageDate:lastMessageDate
                                                                                archivalDate:archivalDate];

    if ([coder decodeObjectForKey:@"archivedAsOfMessageSortId"] != nil) {
        OWSAssertDebug(!_isArchivedObsolete);
        _isArchivedObsolete = YES;
    }

    return self;
}

- (void)anyDidInsertWithTransaction:(DBWriteTransaction *)transaction
{
    [super anyDidInsertWithTransaction:transaction];

    [ThreadAssociatedData createFor:self.uniqueId transaction:transaction];

    if (self.shouldThreadBeVisible && ![SSKPreferences hasSavedThreadWithTransaction:transaction]) {
        [SSKPreferences setHasSavedThread:YES transaction:transaction];
    }

    [self _anyDidInsertWithTx:transaction];

    [SSKEnvironment.shared.modelReadCachesRef.threadReadCache didInsertOrUpdateThread:self transaction:transaction];
}

- (void)anyDidUpdateWithTransaction:(DBWriteTransaction *)transaction
{
    [super anyDidUpdateWithTransaction:transaction];

    if (self.shouldThreadBeVisible && ![SSKPreferences hasSavedThreadWithTransaction:transaction]) {
        [SSKPreferences setHasSavedThread:YES transaction:transaction];
    }

    [SSKEnvironment.shared.modelReadCachesRef.threadReadCache didInsertOrUpdateThread:self transaction:transaction];

    [PinnedThreadManagerObjcBridge handleUpdatedThread:self transaction:transaction];
}

- (BOOL)isNoteToSelf
{
    return NO;
}

- (NSString *)colorSeed
{
    return self.uniqueId;
}

#pragma mark - To be subclassed.

- (NSArray<SignalServiceAddress *> *)recipientAddressesWithSneakyTransaction
{
    __block NSArray<SignalServiceAddress *> *recipientAddresses;
    [SSKEnvironment.shared.databaseStorageRef readWithBlock:^(
        DBReadTransaction *transaction) { recipientAddresses = [self recipientAddressesWithTransaction:transaction]; }];
    return recipientAddresses;
}


- (NSArray<SignalServiceAddress *> *)recipientAddressesWithTransaction:(DBReadTransaction *)transaction
{
    OWSAbstractMethod();

    return @[];
}

- (BOOL)hasSafetyNumbers
{
    return NO;
}

#pragma mark - Interactions

- (nullable TSInteraction *)lastInteractionForInboxWithTransaction:(DBReadTransaction *)transaction
{
    OWSAssertDebug(transaction);
    return [[[InteractionFinder alloc] initWithThreadUniqueId:self.uniqueId]
        mostRecentInteractionForInboxWithTransaction:transaction];
}

- (nullable TSInteraction *)firstInteractionAtOrAroundSortId:(uint64_t)sortId
                                                 transaction:(DBReadTransaction *)transaction
{
    OWSAssertDebug(transaction);
    return
        [[[InteractionFinder alloc] initWithThreadUniqueId:self.uniqueId] firstInteractionAtOrAroundSortId:sortId
                                                                                               transaction:transaction];
}

#pragma mark - Archival

+ (BOOL)legacyIsArchivedWithLastMessageDate:(nullable NSDate *)lastMessageDate
                               archivalDate:(nullable NSDate *)archivalDate
{
    if (!archivalDate) {
        return NO;
    }

    if (!lastMessageDate) {
        return YES;
    }

    return [archivalDate compare:lastMessageDate] != NSOrderedAscending;
}

#pragma mark - Merging

- (void)mergeFrom:(TSThread *)otherThread
{
    self.shouldThreadBeVisible = self.shouldThreadBeVisible || otherThread.shouldThreadBeVisible;
    self.lastInteractionRowId = MAX(self.lastInteractionRowId, otherThread.lastInteractionRowId);

    // Copy the draft if this thread doesn't have one. We always assign both
    // values if we assign one of them since they're related.
    if (self.messageDraft == nil) {
        self.messageDraft = otherThread.messageDraft;
        self.messageDraftBodyRanges = otherThread.messageDraftBodyRanges;
        self.lastDraftInteractionRowId = otherThread.lastDraftInteractionRowId;
        self.lastDraftUpdateTimestamp = otherThread.lastDraftUpdateTimestamp;
    }
}

@end

NS_ASSUME_NONNULL_END
